#ifndef XG_ORACLECONNECT_CPP
#define XG_ORACLECONNECT_CPP
//////////////////////////////////////////////////////////////
#include "../OracleConnect.h"

#ifdef _MSC_VER
#pragma comment(lib, "oci.lib")
#endif

OCIEnv* OracleConnect::s_pEnv = NULL;

OracleRowData::OracleRowData(OracleColumnData* data, ub1* buffer, int cols)
{
	m_iCurIndex = -1;
	m_pBuffer = buffer;
	m_iColumCount = cols;
	m_pOracleColumnData = data;
}
int OracleRowData::getOffset(int index)
{
	if (index >= m_iColumCount)
	{
		return -1;
	}

	int offset = 0;

	for (int i = 0; i < index; i++)
	{
		offset += m_pOracleColumnData[i].size;
	}

	m_iCurIndex = index;

	return offset;
}
OracleRowData::~OracleRowData()
{
	m_pBuffer = NULL;
}
bool OracleRowData::isNull()
{
	return m_pOracleColumnData[m_iCurIndex].indicator) == 0;
}
int OracleRowData::getData(int index, char* data, int len)
{
	int offset = getOffset(index);

	if (offset < 0) return -1;
	if (isNull()) return 0;

	if (len > m_pOracleColumnData[index].indicator)
	{
		len = m_pOracleColumnData[index].indicator;
	}

	memcpy(data, m_pBuffer + offset, len);

	return len;
}
int OracleRowData::getDataLength(int index)
{
	int offset = getOffset(index);
	
	if (offset < 0) return -1;

	return m_pOracleColumnData[index].indicator;
}
string OracleRowData::getString(int index)
{
	int offset = getOffset(index);
	
	if (offset < 0) return stdx::EmptyString();
	if (isNull()) return stdx::EmptyString();

	const char* str = (char*)(m_pBuffer + offset);

	return string(str, str + m_pOracleColumnData[index].indicator);
}
DateTime OracleRowData::getDateTime(int index)
{
	DateTime datetime;

	getDateTime(datetime, index);

	return std::move(datetime);
}
bool OracleRowData::getDateTime(DateTime& datetime, int index)
{
	int offset = getOffset(index);
	
	if (offset < 0) return false;
	if (isNull()) return false;
	
	OCIDate* date = (OCIDate*)(m_pBuffer + offset);
	
	datetime.year = date->OCIDateYYYY - 27800;
	datetime.month = date->OCIDateMM;
	datetime.mday = date->OCIDateDD;
	datetime.yday = CalendarDate::GetYearDay(CalendarDate(datetime.year, datetime.month, datetime.mday));
	datetime.hour = date->OCIDateTime.OCITimeHH - 1;
	datetime.min = date->OCIDateTime.OCITimeMI - 1;
	datetime.sec = date->OCIDateTime.OCITimeSS - 1;

	return true;
}

OracleQueryResult::OracleQueryResult(OCIStmt* stmt, OCIError* err, OracleColumnData* data, int iColunCount, u_char* buffer, int iBufferSize)
{
	m_pErr = err;
	m_stmt = stmt;
	m_pOracleColumnData = data;

	m_bFetchFirst = false;
	m_pSharedBuffer = buffer;
	m_iColumCount = iColunCount;
	m_iBufferSize = iBufferSize;
}
OracleQueryResult::~OracleQueryResult()
{
	close();
}
void OracleQueryResult::close()
{
	if (m_pErr)
	{
		OracleConnect::FreeHandle((dvoid*)(m_pErr), OCI_HTYPE_ERROR);
		m_pErr = NULL;
	}

	if (m_stmt)
	{
		OracleConnect::FreeHandle((dvoid*)(m_stmt), OCI_HTYPE_STMT);
		m_stmt = NULL;
	}

	if (m_pOracleColumnData)
	{
		delete[] m_pOracleColumnData;
		m_pOracleColumnData = NULL;
	}

	if (m_pSharedBuffer)
	{
		delete[] m_pSharedBuffer;
		m_pSharedBuffer = NULL;
	}

	m_pErr = NULL;
	m_iColumCount = -1;
	m_iBufferSize = -1;
}
int OracleQueryResult::rows()
{
	sb4 pos = 0;
	sb4 count = 0;

	if (OCIAttrGet(m_stmt, OCI_HTYPE_STMT, &pos, NULL, OCI_ATTR_CURRENT_POSITION, m_pErr))
	{
		return -1;
	}

	if (OCIStmtFetch2(m_stmt, m_pErr, 1, OCI_FETCH_LAST, 0, OCI_DEFAULT))
	{
		return -1;
	}

	if (OCIAttrGet(m_stmt, OCI_HTYPE_STMT, &count, NULL, OCI_ATTR_CURRENT_POSITION, m_pErr))
	{
		return -1;
	}

	return seek(pos) ? count : -1;
}
int OracleQueryResult::cols()
{
	return m_iColumCount;
}
string OracleQueryResult::getColumnName(int index)
{
	char* str = NULL;
	ub4 size = sizeof(str);
	OCIParam* param = NULL;

	if (OCIParamGet(m_stmt, OCI_HTYPE_STMT, m_pErr, (dvoid**)(&param), index + 1) == 0 && OCIAttrGet(param, OCI_DTYPE_PARAM, (OraText*)(&str), &size, OCI_ATTR_NAME, m_pErr) == 0)
	{
		return string(str);
	}

	return string("");
}
bool OracleQueryResult::getColumnData(ColumnData& data, int index)
{
	ub4 len = 0;
	ub4 size = 0;
	ub2 flag = 0;
	ub2 type = -1;
	char* str = {0};
	OCIParam* param = NULL;
	
	if (OCIParamGet(m_stmt, OCI_HTYPE_STMT, m_pErr, (dvoid**)(&param), index + 1) == 0
		&& OCIAttrGet(param, OCI_DTYPE_PARAM, (OraText*)(&str), &len, OCI_ATTR_NAME, m_pErr) == 0
		&& OCIAttrGet(param, OCI_DTYPE_PARAM, &flag, NULL, OCI_ATTR_IS_NULL, m_pErr) == 0
		&& OCIAttrGet(param, OCI_DTYPE_PARAM, &size, NULL, OCI_ATTR_DATA_SIZE, m_pErr) == 0
		&& OCIAttrGet(param, OCI_DTYPE_PARAM, &type, NULL, OCI_ATTR_DATA_TYPE, m_pErr) == 0)
	{
		data.scale = 0;
		data.size = (int)(size);
		data.type = (int)(type);
		data.nullable = (flag != 0);
		
		for (ub4 i = 0; *str && i < len && i < sizeof(data.name); i++)
		{
			data.name[i] = *str++;
		}

		data.name[sizeof(data.name) - 1] = 0;

		if (SQLT_CHR == type || SQLT_STR == type || SQLT_AFC == type || SQLT_AVC == type)
		{
			data.type = DBData_String;
		}
		else if (SQLT_NUM == type)
		{
			ub2 scale = 0;

			if (OCIAttrGet(param, OCI_DTYPE_PARAM, &scale, NULL, OCI_ATTR_SCALE, m_pErr) == 0)
			{
				if (scale == 0)
				{
					data.type = DBData_Integer;
				}
				else
				{
					data.scale = scale;
					data.type = DBData_Float;
				}
			}
			else
			{
				return false;
			}
		}
		else if (SQLT_DATE == type || SQLT_DAT == type)
		{
			data.type = DBData_DateTime;
		}
		else if (SQLT_BLOB == type || SQLT_BIN == type)
		{
			data.type = DBData_Blob;
		}
		else
		{
			data.type = DBData_Other;
		}
	}

	return true;
}
sp<RowData> OracleQueryResult::next()
{
	u_char* buffer = m_pSharedBuffer;
	OracleColumnData* cs = m_pOracleColumnData;
	ub2 direction = m_bFetchFirst ? OCI_FETCH_FIRST : OCI_FETCH_NEXT;

	if (OCI_NO_DATA == OCIStmtFetch2(m_stmt, m_pErr, 1, direction, 1, OCI_DEFAULT)) return NULL;

	m_bFetchFirst = false;

	return newsp<OracleRowData>(cs, buffer, m_iColumCount);
}
bool OracleQueryResult::seek(int ofs)
{
	if (ofs == 0)
	{
		m_bFetchFirst = true;

		return true;
	}

	m_bFetchFirst = false;

	return OCIStmtFetch2(m_stmt, m_pErr, 1, OCI_FETCH_ABSOLUTE, ofs, OCI_DEFAULT) == 0;
}
int OracleQueryResult::getErrorCode()
{
	sb4 err = 0;

	OCIErrorGet(m_pErr, 1, NULL, &err, NULL, 0, OCI_HTYPE_ERROR);

	return err;
}
string OracleQueryResult::getErrorString()
{
	sb4 err = 0;
	char str[512] = {0};

	OCIErrorGet(m_pErr, 1, NULL, &err, (OraText*)(str), ARR_LEN(str), OCI_HTYPE_ERROR);

	return string(str);
}

bool OracleConnect::FreeHandle(dvoid* handle, int type)
{
	return OCIHandleFree(handle, type) == 0;
}
bool OracleConnect::AllocHandle(dvoid** handle, int type)
{
	return OCIHandleAlloc((dvoid*)(s_pEnv), handle, type, 0, NULL) == 0;
}
int OracleConnect::bind(void* stmt, const vector<DBData*>& vec)
{
	if (vec.size() > 0)
	{
		int res = 0;

		for (size_t i = 0; i < vec.size(); i++)
		{
			OCIBind* bind = NULL;

			assert(vec[i]->type() == DBData_Blob || vec[i]->type() == DBData_String);

			if (vec[i]->isNull())
			{
				res = OCIBindByPos((OCIStmt*)(stmt), &bind, m_pErr, (ub4)(i + 1), 
					(dvoid*)(stdx::EmptyString().c_str()), 0,
					SQLT_STR, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
			}
			else if (vec[i]->type() == DBData_Blob)
			{
				DBBlob* data = (DBBlob*)(vec[i]);

				res = OCIBindByPos((OCIStmt*)(stmt), &bind, m_pErr, (ub4)(i + 1), 
					(dvoid*)(data->val().str()), (sb4)(data->size()),
					SQLT_BIN, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
			}
			else
			{
				DBString* data = (DBString*)(vec[i]);

				res = OCIBindByPos((OCIStmt*)(stmt), &bind, m_pErr, (ub4)(i + 1), 
					(dvoid*)(data->val().c_str()), (sb4)(data->val().length() + 1),
					SQLT_STR, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
			}

			if (res) return SQLRtn_Error;
		}
	}

	return 0;
}
OracleConnect::OracleConnect()
{
	m_pErr = NULL;
	m_pSrv = NULL;
	m_pSvc = NULL;
}
OracleConnect::~OracleConnect()
{
	close();
}
const char* OracleConnect::getSystemName()
{
	return "Oracle";
}
bool OracleConnect::commit()
{
	return OCITransCommit(m_pSvc, m_pErr, 0) == 0;
}
bool OracleConnect::rollback()
{
	return OCITransRollback(m_pSvc, m_pErr, 0) == 0;
}
bool OracleConnect::begin(bool commited)
{
	if (commited) CHECK_FALSE_RETURN(commit());

	return true;
}
void OracleConnect::close()
{
	if (m_pSvc)
	{
		OCILogoff(m_pSvc, m_pErr);
		m_pSvc = NULL;
	}

	if (m_pSrv)
	{
		OCIServerDetach(m_pSrv, m_pErr, OCI_DEFAULT);
		FreeHandle(m_pSrv, OCI_HTYPE_SERVER);
		m_pSrv = NULL;
	}

	if (m_pErr)
	{
		FreeHandle(m_pErr, OCI_HTYPE_ERROR);
		m_pErr = NULL;
	}
}
bool OracleConnect::connect(const string& host, int port, const string& name, const string& user, const string& password)
{
	close();

	if (AllocHandle((dvoid**)(&m_pSrv), OCI_HTYPE_SERVER) && AllocHandle((dvoid**)(&m_pErr), OCI_HTYPE_ERROR))
	{
		char str[512];

		const char* fmt = "//%s:%d/%s";

		sprintf(str, fmt, host.c_str(), port, name.c_str());

#if 0
		return OCIServerAttach(m_pSrv, m_pErr, (OraText*)(str), sb4(strlen(str)), OCI_DEFAULT) == 0 && OCILogon(s_pEnv, m_pErr, &m_pSvc, (OraText*)(user.c_str()), sb4(user.length()), (OraText*)(password.c_str()), sb4(password.length()), NULL, 0) == 0;
#else
		return OCILogon(s_pEnv, m_pErr, &m_pSvc, (OraText*)(user.c_str()), sb4(user.length()), (OraText*)(password.c_str()), sb4(password.length()), (OraText*)(str), sb4(strlen(str))) == 0;
#endif
	}

	return false;
}
int OracleConnect::getPrimaryKeys(vector<string>& vec, const string& tabname)
{
	string sql = "SELECT cu.column_name FROM user_cons_columns cu,user_constraints au WHERE cu.constraint_name=au.constraint_name AND au.constraint_type='P' AND UPPER(au.table_name)=UPPER('" + tabname + "')";

	sp<QueryResult> rs = query(sql);
	
	if (!rs) return -1;

	vec.clear();

	sp<RowData> row;

	while (row = rs->next()) vec.push_back(row->getString(0));

	if (vec.empty())
	{
		sql = "SELECT * FROM " + tabname + " WHERE 1=0";

		if (!(rs = query(sql))) return -1;

		string name;
		int cols = rs->cols();

		for (int i = 0; i < cols; i++)
		{
			if ((name = rs->getColumnName(i)).empty()) return -1;

			vec.push_back(name);
		}
	}

	return vec.size();
}
int OracleConnect::getTables(vector<string>& vec)
{
	const char* sql = "SELECT table_name FROM user_tables";

	sp<QueryResult> rs = query(sql);

	vec.clear();
	
	if (rs)
	{
		sp<RowData> row;

		while (row = rs->next()) vec.push_back(row->getString(0));
	}

	return vec.size();
}
sp<QueryResult> OracleConnect::query(const char* sql)
{
	static vector<DBData*> vec;

	return query(sql, vec);
}
sp<QueryResult> OracleConnect::query(const char* sql, const vector<DBData*>& vec)
{
	OCIStmt* stmt = NULL;
	
	if (!AllocHandle((dvoid**)(&stmt), OCI_HTYPE_STMT))
	{
		updateStatus(SQLRtn_Error, sql, vec);

		return NULL;
	}

	if (OCIStmtPrepare(stmt, m_pErr, (OraText*)(sql), ub4(strlen(sql)), OCI_NTV_SYNTAX, OCI_DEFAULT))
	{
		FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

		updateStatus(SQLRtn_Error, sql, vec);

		return NULL;
	}

	if (bind(stmt, vec))
	{
		updateStatus(SQLRtn_Error, sql, vec);

		return NULL;
	}

	if (OCIStmtExecute(m_pSvc, stmt, m_pErr, memcmp("select", sql, 6) ? 1 : 0, 0, NULL, NULL, OCI_STMT_SCROLLABLE_READONLY) == 0)
	{
		ub4 cols = 0;
		ub4 sz = sizeof(cols);

		if (OCIAttrGet(stmt, OCI_HTYPE_STMT, &cols, &sz, OCI_ATTR_PARAM_COUNT ,m_pErr) == 0 && cols > 0)
		{
			ub4 len = 0;
			ub4 size = 0;
			sb4 type = 0;
			OCIParam* param = NULL;
			OracleColumnData* cs = new OracleColumnData[cols];

			for (ub4 i = 0; i < cols; i++)
			{
				if (OCIParamGet(stmt, OCI_HTYPE_STMT, m_pErr, (dvoid**)(&param), i + 1) == 0
					&& OCIAttrGet(param, OCI_DTYPE_PARAM, &size, NULL, OCI_ATTR_DATA_SIZE, m_pErr) == 0
					&& OCIAttrGet(param, OCI_DTYPE_PARAM, &type, NULL, OCI_ATTR_DATA_TYPE, m_pErr) == 0)
				{
					if (type == SQLT_CHR || type == SQLT_NUM)
					{
						type = SQLT_STR;
					}
					else if (type == SQLT_BLOB)
					{
						type = SQLT_BIN;
					}

					len += size;
					cs[i].size = ub2(size);
					cs[i].type = ub2(type);
				}
				else
				{
					FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

					updateStatus(SQLRtn_Error, sql, vec);

					delete[] cs;

					return NULL;
				}
			}

			u_char* buffer = new u_char[len];

			len = 0;

			for (ub4 i = 0; i < cols; i++)
			{
				if (OCIDefineByPos(stmt, &(cs[i].handle), m_pErr, i + 1, buffer + len, cs[i].size, cs[i].type, NULL, &(cs[i].indicator), NULL, OCI_DEFAULT))
				{
					FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

					updateStatus(SQLRtn_Error, sql, vec);

					delete[] buffer;
					delete[] cs;

					return NULL;
				}

				len += cs[i].size;
			}

			OCIError* err = NULL;

			if (AllocHandle((dvoid**)(&err), OCI_HTYPE_ERROR))
			{
				sp<OracleQueryResult> rs = newsp<OracleQueryResult>(stmt, err, cs, cols, buffer, len);
				
				updateStatus(rs->rows(), sql, vec);

				return rs;
			}

			FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

			updateStatus(SQLRtn_Error, sql, vec);

			delete[] buffer;
			delete[] cs;

			return NULL;
		}
	}

	FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

	updateStatus(SQLRtn_Error, sql, vec);

	return NULL;
}
int OracleConnect::execute(const char* sql)
{
	return exec(sql);
}
int OracleConnect::execute(const char* sql, const vector<DBData*>& vec)
{
	int res = 0;
	OCIStmt* stmt = NULL;

	if (!AllocHandle((dvoid**)(&stmt), OCI_HTYPE_STMT))
	{
		updateStatus(SQLRtn_Error, sql, vec);

		return SQLRtn_Error;
	}

	if (OCIStmtPrepare(stmt, m_pErr, (OraText*)(sql), ub4(strlen(sql)), OCI_NTV_SYNTAX, OCI_DEFAULT))
	{
		FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

		updateStatus(SQLRtn_Error, sql, vec);

		return SQLRtn_Error;
	}

	if (bind(stmt, vec))
	{
		updateStatus(SQLRtn_Error, sql, vec);

		return SQLRtn_Error;
	}

	res = OCIStmtExecute(m_pSvc, stmt,
			m_pErr, memcmp("select", sql, 6) ? 1 : 0, 
			0, NULL, NULL, OCI_STMT_SCROLLABLE_READONLY);
	
	if (res == 0)
	{
		sb4 rows = -1;
		
		if (OCIAttrGet(stmt, OCI_HTYPE_STMT, &rows, 0, OCI_ATTR_ROW_COUNT, m_pErr) == 0)
		{
			FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

			updateStatus(rows, sql, vec);

			return rows;
		}
	}

	updateStatus(SQLRtn_Error, sql, vec);

	FreeHandle((dvoid*)(stmt), OCI_HTYPE_STMT);

	if (res == OCI_NO_DATA) return SQLRtn_NotFound;

	if ((res = getErrorCode()) == 1) return SQLRtn_Duplicate;

	return SQLRtn_Error;
}
int OracleConnect::getErrorCode()
{
	if (m_pSvc == NULL) return XG_ERROR;

	sb4 err = 0;

	OCIErrorGet(m_pErr, 1, NULL, &err, NULL, 0, OCI_HTYPE_ERROR);

	return err;
}
string OracleConnect::getErrorString()
{
	if (m_pSvc == NULL) return stdx::EmptyString();

	sb4 err = 0;
	char str[512] = {0};

	OCIErrorGet(m_pErr, 1, NULL, &err, (OraText*)(str), ARR_LEN(str), OCI_HTYPE_ERROR);

	return string(str);
}
bool OracleConnect::Setup()
{
	if (s_pEnv == NULL) OCIEnvCreate(&s_pEnv, OCI_DEFAULT, NULL, 0, 0, 0, 0, NULL);

	return s_pEnv ? true : false;
}
//////////////////////////////////////////////////////////////
#endif
